using Cysharp.Threading.Tasks;
using IQIGame.Onigao.Framework;
using IQIGame.Onigao.GameAOT;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace IQIGame.Onigao.GamePlay
{
    /// <summary>
    /// 场景分块控制器，可以复用
    /// </summary>
    public class SceneBlockController : IDisposable
    {
        // 分块刷新间隔计时器
        private float _blockCheckTimer = 0;

        // 分块场景适配器
        private ISceneBlockAdapter _sceneBlockAdapter;

        // 分块场景的配置
        private SceneBlockConfig _sceneBlockConfig;

        // 当前加载的所有分块
        private Dictionary<int, SceneBlock> _sceneBlocks = new Dictionary<int, SceneBlock>();

        // 临时Set，表示需要删除的分块
        private HashSet<int> _needDeleteBlocks = new HashSet<int>();

        // SceneBlock的加载队列，添加加载间隔，防止卡顿
        private readonly List<SceneBlock> _blockLoadQueue = new List<SceneBlock>();
        private readonly HashSet<int> _blockInLoading = new HashSet<int>();
        private float _blockLoadTimer = 0;
        private Action<bool, GameObject> _onSceneBlockChanged;

        public SceneBlockController(ISceneBlockAdapter sceneBlockAdapter, SceneBlockConfig sceneBlockConfig, Action<bool, GameObject> onSceneBlockChanged)
        {
            this._sceneBlockAdapter = sceneBlockAdapter;
            this._sceneBlockConfig = sceneBlockConfig;
            this._onSceneBlockChanged = onSceneBlockChanged;
        }

        /// <summary>
        /// 一次完整的异步分块加载，并且等待完成，适用于切换场景或者传送地图，需要有加载界面
        /// </summary>
        /// <returns></returns>
        public UniTask LoadSceneBlockOnceAsync(Vector3 pos)
        {
            CalcBlockIndex(pos, out var x, out var y, out var fx, out var fy);

            _needDeleteBlocks.Clear();
            _needDeleteBlocks.AddRangeKey(_sceneBlocks);

            List<UniTask> taskList = new List<UniTask>();

            for (int i = -SceneBlockUtility.LoadMapRange; i <= SceneBlockUtility.LoadMapRange; i++)
            {
                for (int j = -SceneBlockUtility.LoadMapRange; j <= SceneBlockUtility.LoadMapRange; j++)
                {
                    int curx = x + i;
                    int cury = y + j;
                    if (curx < 0 || curx >= _sceneBlockConfig.sizeX || cury < 0 || cury >= _sceneBlockConfig.sizeY)
                    {
                        continue;
                    }
                    int id = SceneBlockUtility.SceneBlockXY2Id(curx, cury);
                    if (!_sceneBlocks.ContainsKey(id))
                    {
                        var block = ClassPool.Get<SceneBlock>();
                        block.InitSceneBlock(id, curx, cury, _sceneBlockConfig);
                        if (block.CheckHasBlockResource())
                        {
                            taskList.Add(LoadSceneBlock(block));
                        }
                        _sceneBlocks.Add(id, block);
                    }
                    else
                    {
                        _needDeleteBlocks.Remove(id);
                    }
                }
            }

            UnloadSceneBlockOutRange(fx, fy);

            return UniTask.WhenAll(taskList.ToArray());
        }

        /// <summary>
        /// 刷新分块的加载
        /// 会把需要加载的块放入加载队列中
        /// </summary>
        /// <param name="pos">角色位置</param>
        private void UpdateSceneBlockLoad(Vector3 pos)
        {

            CalcBlockIndex(pos, out var x, out var y, out var fx, out var fy);

            _needDeleteBlocks.Clear();
            _needDeleteBlocks.AddRangeKey(_sceneBlocks);

            for (int i = -SceneBlockUtility.LoadMapRange; i <= SceneBlockUtility.LoadMapRange; i++)
            {
                for (int j = -SceneBlockUtility.LoadMapRange; j <= SceneBlockUtility.LoadMapRange; j++)
                {
                    int curx = x + i;
                    int cury = y + j;
                    if (curx < 0 || curx >= _sceneBlockConfig.sizeX || cury < 0 || cury >= _sceneBlockConfig.sizeY)
                    {
                        continue;
                    }
                    int id = SceneBlockUtility.SceneBlockXY2Id(curx, cury);
                    if (!_sceneBlocks.ContainsKey(id))
                    {
                        var block = ClassPool.Get<SceneBlock>();
                        block.InitSceneBlock(id, curx, cury, _sceneBlockConfig);
                        if (block.CheckHasBlockResource())
                        {
                            _blockLoadQueue.Add(block);
                            _blockInLoading.Add(block.id);
                        }

                        _sceneBlocks.Add(id, block);
                    }
                    else
                    {
                        _needDeleteBlocks.Remove(id);
                    }
                }
            }

            UnloadSceneBlockOutRange(fx, fy);
        }

        /// <summary>
        /// 根据坐标(一般是角色位置)，算出所在的分块
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        private void CalcBlockIndex(Vector3 pos, out int x, out int y, out float fx, out float fy)
        {
            fx = Mathf.Abs(pos.x + _sceneBlockConfig.offsetPosition.x) / SceneBlockUtility.BlockSize;
            fy = Mathf.Abs(pos.z + _sceneBlockConfig.offsetPosition.y) / SceneBlockUtility.BlockSize;
            x = Mathf.FloorToInt(fx);
            y = Mathf.FloorToInt(fy);
        }

        private async UniTask LoadSceneBlock(SceneBlock sceneBlock)
        {
            await sceneBlock.LoadSceneBlockAsync();
            if (sceneBlock.gameObject != null)
            {
                _onSceneBlockChanged?.Invoke(false, sceneBlock.gameObject);
            }
        }


        private void UnLoadSceneBlock(SceneBlock sceneBlock)
        {
            if (sceneBlock.gameObject != null)
            {
                _onSceneBlockChanged?.Invoke(true, sceneBlock.gameObject);
            }
            var deleteId = sceneBlock.id;
            if (_blockInLoading.Contains(deleteId))
            {
                _blockInLoading.Remove(deleteId);
                _blockLoadQueue.Remove(sceneBlock);
            }
            ClassPool.Put<SceneBlock>(sceneBlock);
            _sceneBlocks.Remove(deleteId);
        }

        /// <summary>
        /// 卸载视野之外的分块
        /// </summary>
        /// <param name="fx"></param>
        /// <param name="fy"></param>
        private void UnloadSceneBlockOutRange(float fx, float fy)
        {
            if (_needDeleteBlocks.Count <= 0)
            {
                return;
            }
            float nowTime = Time.realtimeSinceStartup;
            // 删除视野之外的所有格子
            foreach (var deleteId in _needDeleteBlocks)
            {
                if (!_sceneBlocks.TryGetValue(deleteId, out var sceneBlock))
                {
                    continue;
                }

                // 还没加载，可以直接卸载
                if (sceneBlock.loadState == SceneBlockLoadState.Init)
                {
                    UnLoadSceneBlock(sceneBlock);
                    continue;
                }

                // 判断距离是否额外超出,因为格子是以角落为中心，所以需要+0.5转为中心计算
                var x = Mathf.Max(0, Mathf.Abs(fx - (sceneBlock.x + 0.5f)) - SceneBlockUtility.LoadMapRange - 0.5f);
                var y = Mathf.Max(0, Mathf.Abs(fy - (sceneBlock.y + 0.5f)) - SceneBlockUtility.LoadMapRange - 0.5f);

                if (x > 1 || y > 1)
                {
                    // 额外多超过了一个格子，直接卸载
                    UnLoadSceneBlock(sceneBlock);
                    continue;
                }
                if (nowTime < sceneBlock.existTime)
                {
                    // 存活时间还没到，不卸载
                    continue;
                }
                // 超出边缘一点范围，可以卸载了
                if (x > SceneBlockUtility.BlockUnloadExtraRatio || y > SceneBlockUtility.BlockUnloadExtraRatio)
                {
                    UnLoadSceneBlock(sceneBlock);
                    continue;
                }
            }
        }

        /// <summary>
        /// 取消并且移除正在加载的分块
        /// 可以用于同场景中有Loading的传送。先取消加载，然后调用LoadSceneBlockOnceAsync一次加载好，然后移除Loading界面。
        /// </summary>
        public void CancelAndRemoveLoadingBlock()
        {
            foreach (var sceneBlock in _blockLoadQueue)
            {
                ClassPool.Put<SceneBlock>(sceneBlock);
                _sceneBlocks.Remove(sceneBlock.id);
            }
            _blockLoadQueue.Clear();
            _blockInLoading.Clear();
        }

        private void LoadQueueUpdate(float deltaTime)
        {
            if (_blockLoadQueue.Count == 0)
            {
                return;
            }

            if (_blockLoadTimer > 0)
            {
                _blockLoadTimer -= deltaTime;
                return;
            }

            var block = _blockLoadQueue[0];
            switch (block.loadState)
            {
                case SceneBlockLoadState.Init:
                    LoadSceneBlock(block).Forget();
                    break;
                case SceneBlockLoadState.Loading:
                    break;
                case SceneBlockLoadState.LoadComplete:
                    _blockLoadQueue.Remove(block);
                    _blockInLoading.Remove(block.id);
                    // 加载完后有个间隔时间，才能加载下一个
                    _blockLoadTimer = SceneBlockUtility.BlockLoadInstantiateInterval;
                    break;
            }
        }

        /// <summary>
        /// 地图分块加载Update
        /// </summary>
        public void Update(float deltaTime)
        {
            LoadQueueUpdate(deltaTime);

            // 过一段时间刷新一次分块
            _blockCheckTimer += deltaTime;
            if (_blockCheckTimer < SceneBlockUtility.BlockLoadCheckInterval)
            {
                return;
            }
            _blockCheckTimer = 0;

            var position = _sceneBlockAdapter.GetCenterPosition();
            UpdateSceneBlockLoad(position);
        }

        public void ClearAllBlock()
        {
            foreach (var block in _sceneBlocks.Values)
            {
                ClassPool.Put<SceneBlock>(block);
            }
            _sceneBlocks.Clear();
        }

        /// <summary>
        /// 等待更新中的加载完成
        /// 注意：该方法对进场景的首次加载无效
        /// </summary>
        /// <returns></returns>
        public async UniTask AwaitForLoadingOnUpdate()
        {
            if (_blockCheckTimer != 0)
            {
                float leftCheckTime = SceneBlockUtility.BlockLoadCheckInterval - _blockCheckTimer;
                leftCheckTime = Math.Max(0, leftCheckTime);
                if (leftCheckTime > 0)
                {
                    await UniTask.Delay((int)(leftCheckTime * 1000));
                }
            }
            else
            {
                await UniTask.DelayFrame(1);
            }
            await UniTask.WaitUntil(NoLoadingBlocks);
        }

        private bool NoLoadingBlocks()
        {
            return _blockLoadQueue.Count == 0;
        }

        public void Dispose()
        {
            ClearAllBlock();
        }
    }
}
